<script setup lang="ts">
import { ref } from "vue";

// 1. 申明
type HasSides = { numberOfSides: number };
type SidesHaveLength = { sideLength: number };

// 2. 使用
const logPerimeter = <Sharp extends HasSides & SidesHaveLength>(s: Sharp) => {
  console.log(s.numberOfSides * s.sideLength);
  return s;
};

// 1. 申明
type Square = HasSides & SidesHaveLength;

// 2. 使用
const square: Square = { numberOfSides: 10, sideLength: 20 };

type State = {
  [key: string]: string;
};

interface StringDatabaseInterface {
  state: State;

  get(key: string): string | null;

  set(key: string, value: string): void;
}

interface StringDatabaseConstructor {
  new (): StringDatabase;

  from(state: State): StringDatabase;
}

class StringDatabase implements StringDatabaseInterface {
  state: State = {};

  constructor(state: State = {}) {
    this.state = state;
  }

  get(key: string): string | null {
    return key in this.state ? this.state[key] : null;
  }

  set(key: string, value: string): void {
    this.state[key] = value;
  }

  from(state: State) {
    const db = new StringDatabase();
    for (const key in state) {
      db.set(key, state[key]);
    }
    return db;
  }
}

class MyMap<K, V> {
  constructor(initialKey: K, initialValue: V) {
    // any code.
  }
}

// 混入测试.

// 不知道构造参数需要传几个进来。
type ClassConstructor<T> = new (...args: any[]) => T;

// 这里的debug传入了一个泛型参数。
function withEZDebug<C extends ClassConstructor<{ getDebugValue(): object }>>(
  Class: C
) {
  return class extends Class {
    constructor(...args: any[]) {
      super(...args);
    }
    // 混入的方法
    debug() {
      const Name = Class.constructor.name;
      const value = this.getDebugValue();
      return Name + "(" + JSON.stringify(value) + ")";
    }
  };
}

class HardToDebugUser {
  constructor(
    private id: number,
    private firstName: string,
    private lastName: string
  ) {}

  // 混入方法的数据来源.
  getDebugValue() {
    return {
      id: this.id,
      name: this.firstName + " " + this.lastName,
    };
  }
}

// 用户可以有这些属性， 管理员可以有这些属性。同一级别有相同属性/方法的，又不属于父类公共属性/方法的，都可以使用混入来解决。
const User = withEZDebug(HardToDebugUser);
const user = new User(3, "Emma", "Gluzman");

user.debug();

// 装饰器测试.
// 使用装饰器，简化混入(注解代替了复杂的类调用).
// angular基于大量的装饰器，实现了复杂的代码.
@withEZDebug
class APIPayload {
  constructor(
    private id: number,
    private firstName: string,
    private lastName: string
  ) {}

  // 混入方法的数据来源.
  getDebugValue() {
    return {
      id: this.id,
      name: this.firstName + " " + this.lastName,
    };
  }
}

// 装饰器测试2.
function addAge(constructor: any) {
  constructor.prototype.age = 18;
}

@addAge
class Person {
  name: string;
  age!: number;

  constructor() {
    this.name = "huihui";
  }
}

const person = new Person();

console.log(person.age); // 18
console.log(logPerimeter(square));

class Teacher {}

const test = new Function("name", "return 'hello' + name");

test("123456");

defineProps<{ msg: string }>();

const count = ref(0);

// 创建接口.
interface Shoe {
  purpose: string;
}

// 第一种实现.
class BalletFlat implements Shoe {
  purpose = "dancing";
}

// 第二种实现.
class Boot implements Shoe {
  purpose = "woodcutting";
}

// 添加类型检查
type CreateShoe = {
  (type: "BalletFlat"): BalletFlat;
  (type: "Boot"): Boot;
};

// 工厂模式
class ShoeFactory {
  create: CreateShoe = (type: "BalletFlat" | "Boot") => {
    switch (type) {
      case "BalletFlat":
        return new BalletFlat();
      case "Boot":
        return new Boot();
    }
  };
}

const shoeCreate = new ShoeFactory().create("BalletFlat");

interface Method {
  setMethod(method: "get" | "post"): Data;
}

interface Data {
  setData(data: object): Url;
}

interface Url {
  setUrl(url: string): RequestMethod;
}

// 利用 Omit 保证方法仅执行一次.
interface RequestMethod {
  send<T>(this: T): Omit<T, "send">;

  cancel<T>(this: T): Omit<T, "cancel">;
}

type IRequest = Method & Data & Url & RequestMethod;

// 使用建造者模式，生成链式函数，使代码更简洁、更易懂.
// 建造者的涉及应该建立在不影响原类的基础上.即，满足单一职责.
class Request implements IRequest {
  private data: object | null = null;
  private method: "get" | "post" | null = null;
  private url: string | null = null;

  private constructor() {
    // ...
  }
  static builder(): Method {
    return new Request();
  }

  setData(data: object): Url {
    this.data = data;
    return this;
  }

  setMethod(method: "get" | "post"): Data {
    this.method = method;
    return this;
  }

  setUrl(url: string): RequestMethod {
    this.url = url;
    return this;
  }

  send<T>(this: T): Omit<T, "send"> {
    // ...
    return this;
  }

  cancel<T>(this: T): Omit<T, "cancel"> {
    // ...
    return this;
  }
}

const request = Request.builder()
  .setMethod("get")
  .setData({})
  .setUrl("test")
  .send()
  .cancel();

class Axios {
  private constructor() {
    // ...
  }

  static getInstance() {
    return new Axios();
  }
}
Axios.getInstance();
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>

  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
      >create-vue</a
    >, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>
